package hypeerweb;

import java.sql.*;

import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class HyPeerWebDatabase {

	private static final String DATABASE_DIRECTORY = "Databases/";
	private static final String DEFAULT_DATABASE_NAME = "HyPeerWeb.sqlite";
	private static HyPeerWebDatabase singleton;

	private Map<Integer, Node> nodes;
	private String dbname;

	private HyPeerWebDatabase(String dbname) {
		this.dbname = dbname;
	}

	public static HyPeerWebDatabase getSingleton() {
		if (singleton == null) {
			singleton = new HyPeerWebDatabase(DEFAULT_DATABASE_NAME);
		}
		return singleton;
	}

	public static void initHyPeerWebDatabase() {
		initHyPeerWebDatabase("");
	}

	// this will create a new database file if one does not already exist
	public static void initHyPeerWebDatabase(String dbName) {
		if (dbName == null || dbName.length() == 0) {
			dbName = DEFAULT_DATABASE_NAME;
		}
		getSingleton();
		singleton.dbname = dbName;

		try {

			File file = new File(DATABASE_DIRECTORY + dbName);
			boolean fileExists = file.exists();
			Connection connection = connect(dbName);

			Statement stat = connection.createStatement();
			if (fileExists) {
				singleton.readInNodes(stat);
			} else {
				stat.executeUpdate("create table Nodes(WebID INTEGER PRIMARY KEY, Height INTEGER,"
						+ "Fold INTEGER, SurrogateFold INTEGER, InvSurrFold INTEGER, Child INTEGER, Parent INTEGER);");
				stat.executeUpdate("create table Neighbors(WebID INTEGER, NeighborID INTEGER, PRIMARY KEY(WebID, NeighborID));");
				stat.executeUpdate("create table SurrogateNeighbors(WebID INTEGER, NeighborID INTEGER, PRIMARY KEY(WebID, NeighborID));");
				singleton.nodes = new HashMap<Integer, Node>();
			}
			stat.close();
			connection.close();

		} catch (SQLException e) {
			// TODO handle exception gracefully
			e.printStackTrace();
			return;
		}
	}

	private void readInNodes(Statement stat) throws SQLException {
		nodes = new HashMap<Integer, Node>();
		ResultSet results = stat.executeQuery("select * from Nodes");
		Node node;
		while (results.next()) {
			int id = results.getInt("WebID");
			int height = results.getInt("Height");
			int fold = results.getInt("Fold");
			int surrFold = results.getInt("SurrogateFold");
			int invSurrFold = results.getInt("InvSurrFold");

			node = nodes.get(id);
			if (node == null) {
				node = new Node(id, height);
				nodes.put(id, node);
			}
			Node other;
			if (fold >= 0) {
				other = nodes.containsKey(fold) ? nodes.get(fold) : new Node(
						fold);
				node.setFold(other);
			} else {
				node.setFold(Node.NULL_NODE);
			}
			if (surrFold >= 0) {
				other = nodes.containsKey(surrFold) ? nodes.get(surrFold)
						: new Node(surrFold);
				node.setSurrogateFold(other);
			} else {
				node.setSurrogateFold(Node.NULL_NODE);
			}
			if (invSurrFold >= 0) {
				other = nodes.containsKey(invSurrFold) ? nodes.get(invSurrFold)
						: new Node(invSurrFold);
				node.setInverseSurrogateFold(other);
			} else {
				node.setInverseSurrogateFold(Node.NULL_NODE);
			}
		}
		results.close();

		results = stat.executeQuery("select * from Neighbors");
		while (results.next()) {
			node = nodes.get(results.getInt("WebID"));
			Node neighbor = nodes.get(results.getInt("NeighborID"));
			// Do we want addNeighbor to add neighbors both ways?
			node.addNeighbor(neighbor);
			// neighbor.addNeighbor(node);
		}
		results.close();

		results = stat.executeQuery("select * from SurrogateNeighbors");
		while (results.next()) {
			node = nodes.get(results.getInt("WebID"));
			Node neighbor = nodes.get(results.getInt("NeighborID"));

			node.addDownPointer(neighbor);
			neighbor.addUpPointer(node);
		}
		results.close();

	}

	public Collection<Node> getNodes() {
		return nodes.values();
	}

	public SimplifiedNodeDomain getNode(int webId) {
		return null;
	}

	private static Connection connect(java.lang.String dbName) {

		try {
			Class.forName("org.sqlite.JDBC");
			File file = new File(DATABASE_DIRECTORY + dbName);
			String absolutePath = file.getAbsolutePath();
			Connection connection = DriverManager.getConnection("jdbc:sqlite:/"
					+ absolutePath);
			return connection;
		} catch (SQLException e) {
			// TODO handle exception gracefully
			e.printStackTrace();
			return null;
		} catch (ClassNotFoundException e) {
			// TODO handle exception gracefully
			e.printStackTrace();
			return null;
		}
	}

	public void clear() {
		Connection connect = connect(dbname);
		try {
			Statement stat = connect.createStatement();
			stat.executeUpdate("delete from Nodes;");
			stat.executeUpdate("delete from Neighbors;");
			stat.executeUpdate("delete from SurrogateNeighbors;");
		} catch (SQLException e) {
			e.printStackTrace();
		}

	}

	public void storeNode(Node node) throws SQLException {

		
		Statement stmt = null;
		String query = "INSERT OR REPLACE INTO Nodes (" + "WebID," + "Fold,"
				+ "SurrogateFold," + "Height," + "InvSurrFold" + ") VALUES ("
				+ "'" + node.getId() + "'," + "'" + node.getFold().getId()
				+ "'," + "'" + node.getSurrogateFold().getId() + "'," + "'"
				+ node.getHeight() + "'," + "'"
				+ node.getInverseSurrogateFold().getId() + "'" + ");";

		// System.out.println(query);

		try {
			Connection connection = connect(dbname);
			stmt = connection.createStatement();
			stmt.executeUpdate(query);
			connection.close();

			storeNeighbors(node, null);
			storeNeighbors(node, "SurrogateNeighbors");
		} catch (SQLException e) {
			// TODO handle exception gracefully
			e.printStackTrace();
		}
	}

	public void storeNeighbors(Node node, String tableName) throws SQLException {

		List<Node> neighbors;

		if (tableName != "SurrogateNeighbors") {
			tableName = "Neighbors";
			neighbors = node.getNeighbors();
		} else {
			neighbors = node.getDownPointers();
		}

		Iterator<Node> iter = neighbors.iterator();

		Connection connection = connect(dbname);
		Statement stmt = connection.createStatement();

		while (iter.hasNext()) {
			Node n = iter.next();

			String query = "INSERT OR REPLACE INTO " + tableName + " ("
					+ "WebID," + "NeighborID" + ") VALUES (" + "'"
					+ node.getId() + "'," + "'" + n.getId() + "'" + ")";

			// System.out.println(query);
			stmt.executeUpdate(query);
		}

		connection.close();
	}
}
